/*
 * SpeciesUtils.cpp
 *
 *  Created on: 22 Aug 2011
 *      Author: Allan
 */

#include "SpeciesUtils.h"

// C++ includes
#include <set>

// Boost includes
#include <boost/foreach.hpp>

// GeoReact includes
#include "StringUtils.h"
#include "../Database.h"
#include "../Utilities/Algorithms.h"

const vector<string> DetermineSpecies(const vector<string>& species)
{
	vector<string> basisSpecies = DetermineBasisSpecies(species);
	
	vector<string> secondarySpecies = DetermineSecondarySpecies(species);
	
	return Union(Union(basisSpecies, secondarySpecies), species);
}

const vector<string> DetermineAqueousSpecies(const vector<string>& species)
{
	return FilterSpecies(DetermineSpecies(species), {"aqueous"});
}

const vector<string> DetermineGaseousSpecies(const vector<string>& species)
{
	return FilterSpecies(DetermineSpecies(species), {"gaseous"});
}

const vector<string> DetermineMineraSpecies(const vector<string>& species)
{
	return FilterSpecies(DetermineSpecies(species), {"mineral"});
}

const vector<string> DetermineBasisSpecies(const vector<string>& species)
{
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// The set containing only unique basis species
	set<string> basisSpecies;
	
	// Make sure the hydrogen ion H[+] is present in the set of basis species in case H2O is listed in (species)
	if(IndexOf("H2O", species) < species.size())
		basisSpecies.insert("H[+]");
	
	// Loop over all the provided (species)
	BOOST_FOREACH(const string& s, species)
	{
		// Check if the current known species is a basis species in the database
		if(db.GetSpeciesInfo(s).HasTag("basis"))
			basisSpecies.insert(s);
		else
		{
			// Get the info data of the reaction whose reactant is the current known species
			const ReactionInfo reactionInfo = db.GetReactionInfo(s);
		
			// Add all the products in the reaction to the set of basis species being created (all these products are basis species in the database)
			basisSpecies.insert(reactionInfo.products.begin(), reactionInfo.products.end());
		}
	}
	
	// Convert the set of basis species to a vector container and return it
	return vector<string>(basisSpecies.begin(), basisSpecies.end());
}

const vector<string> DetermineSecondarySpecies(const vector<string>& species)
{
	const vector<string> basisSpecies = DetermineBasisSpecies(species);
	
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// Get a reference to the list of reactions in the database
	const Database::ReactionInfoList& reactionInfoList = db.GetReactionInfoList();
	
	// The vector of secondary species that can be built from the set of basis species
	vector<string> secondarySpecies;
	
	// Loop over all the reactions in the database
	BOOST_FOREACH(const ReactionInfo& r, reactionInfoList)
	{
		// An auxiliary boolean variable to determine if all the products of the current reaction belong to the set of basis species
		bool belong = true;
		
		//  Check if the all the products of the current reaction belong to the set of basis species (basisSpecies)
		BOOST_FOREACH(const string& product, r.products)
			if(!IsContained(product, basisSpecies))
				belong = false;
		
		// Insert the reactant (a secondary species) of the current reaction into the vector of secondary species
		if(belong)
			secondarySpecies.push_back(r.reactant);
	}
	
	return secondarySpecies;
}

const vector<string> FilterIons(const vector<string>& species)
{
	// The list of aqueous ions from the given list of species
	vector<string> ions;
	
	// Loop over all the species and check which of them is an aqueous ion
	BOOST_FOREACH(const string& s, species)
		if(s.find("+") != s.npos || s.find("-") != s.npos)
			ions.push_back(s);
	
	return ions;
}

const vector<string> FilterComplexes(const vector<string>& species)
{
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// The list of aqueous complexes from the given list of (species)
	vector<string> complexes;
	
	// Loop over all the species and check which of them is an aqueous complex
	BOOST_FOREACH(auto s, species)
		if(db.FindComplexInfo(s))
			complexes.push_back(s);
	
	return complexes;
}

const vector<string> FilterBasisSpecies(const vector<string>& species)
{
	return FilterSpecies(species, {"basis"});
}

const vector<string> FilterSecondarySpecies(const vector<string>& species)		
{
	return RemoveSpecies(species, {"basis"});
}

const vector<string> FilterSpecies(const vector<string>& species, const vector<string>& tags)
{
	// The subset of the given species that have any of the given tags
	vector<string> newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}

const vector<string> RemoveSpecies(const vector<string>& species, const vector<string>& tags)
{
	// The subset of the given species that does not have any of the given tags
	vector<string> newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(!Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}

const double ElectricalCharge(const string& ion)
{
	vector<string> words = Split(ion, "[]");
	
	if(words.size() == 1) return 0;
	
	if(words[1] == "+") return +1;
	if(words[1] == "-") return -1;
	
	const string strCharge = {words[1][1], words[1][0]};
	
	const double charge = atof(strCharge.c_str());
	
	return charge;
}
